package com.ikingtech.datav.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ikingtech.datav.enums.FilterTypeEnum;
import com.ikingtech.datav.exception.InfoExceptionInfo;
import com.ikingtech.datav.mapper.InfoMapper;
import com.ikingtech.datav.model.bo.EditorPage;
import com.ikingtech.datav.model.constant.FileConstant;
import com.ikingtech.datav.model.entity.ComponentDO;
import com.ikingtech.datav.model.entity.FilterDO;
import com.ikingtech.datav.model.entity.InfoDO;
import com.ikingtech.datav.model.entity.TemplateComponentDO;
import com.ikingtech.datav.model.request.InfoDTO;
import com.ikingtech.datav.model.request.InfoSearchDTO;
import com.ikingtech.datav.model.result.InfoVO;
import com.ikingtech.datav.model.result.TemplateVO;
import com.ikingtech.datav.utils.Util;
import com.ikingtech.framework.sdk.base.model.PageResult;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.department.api.DeptApi;
import com.ikingtech.framework.sdk.department.model.DeptBasicDTO;
import com.ikingtech.framework.sdk.department.model.DeptQueryParamDTO;
import com.ikingtech.framework.sdk.utils.Tools;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author fucb
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class InfoService extends ServiceImpl<InfoMapper, InfoDO> {

    private final DeptApi deptApi;

    private final ComponentService componentService;

    private final TemplateService templateService;

    private final TemplateComponentService templateComponentService;

    private final FilterService filterService;

    public PageResult<InfoVO> selectByPage(InfoSearchDTO queryParam) {
        List<String> list;
        if (Me.isAdmin()) {
            DeptQueryParamDTO dto = new DeptQueryParamDTO();
            dto.setParentDeptId(queryParam.getGroupId());
            list = deptApi.listSubInfoAll(dto).getData().stream().map(DeptBasicDTO::getId).collect(Collectors.toList());
        } else {
            list = Me.dataScope(queryParam.getGroupId());
        }
        list.add(queryParam.getGroupId());

        Page<InfoDO> page = this.page(new Page<>(queryParam.getPage(), queryParam.getRows()), Wrappers.<InfoDO>lambdaQuery()
                .in(Tools.Coll.isNotBlank(list), InfoDO::getGroupId, list)
                .like(Tools.Str.isNotBlank(queryParam.getName()), InfoDO::getName, queryParam.getName())
                .last(CharSequenceUtil.isNotBlank(queryParam.getOrder()), CharSequenceUtil.toUnderlineCase(Tools.Str.join("order by ", " desc", queryParam.getOrder()))));

        PageResult<InfoDO> pageResult = PageResult.build(page);
        return pageResult.convert(this::convertVO);
    }

    public InfoVO getInfo(String id) {
        InfoDO entity = this.baseMapper.selectById(id);
        if (null == entity) {
            throw new FrameworkException(InfoExceptionInfo.INFO_NOT_FOUND);
        }
        return this.convertVO(entity);
    }

    public InfoVO convertVO(InfoDO entity) {
        InfoVO infoVO = Tools.Bean.copy(entity, InfoVO.class);
        infoVO.setDialogs(Util.Json.toBean(entity.getDialogs()));
        infoVO.setEvents(Util.Json.toBean(entity.getEvents()));
        infoVO.setHost(Util.Json.toBean(entity.getHost()));
        infoVO.setIframe(Util.Json.toBean(entity.getIframe()));
        infoVO.setPages(Util.Json.toBean(entity.getPages()));
        infoVO.setStyleFilterParams(Util.Json.toBean(entity.getStyleFilterParams()));
        infoVO.setVariables(Util.Json.toBean(entity.getVariables()));
        infoVO.setAddress(Util.Json.toBean(entity.getAddress()));
        return infoVO;
    }

    @Transactional(rollbackFor = Exception.class)
    public InfoDO add(InfoDTO param) {
        InfoDO info = this.supply(param);
        String uuid = IdUtil.simpleUUID();
        info.setId(uuid);
        info.setShareUrl(this.getShareUrl(uuid));
        this.baseMapper.insert(info);
        return info;
    }

    private String getShareUrl(String uuid) {
        return "/share/#/screen/" + uuid;
    }

    public void add(InfoDO param) {
        this.baseMapper.insert(param);
    }

    public void edit(InfoDTO param) {
        InfoDO supply = this.supply(param);
        if (!this.exist(param.getId())) {
            throw new FrameworkException(InfoExceptionInfo.INFO_NOT_FOUND);
        }
        this.baseMapper.updateById(supply);
    }

    @Transactional(rollbackFor = Exception.class)
    public void delete(String id) {
        this.baseMapper.deleteById(id);
    }

    public boolean exist(String id) {
        return this.baseMapper.exists(Wrappers.<InfoDO>lambdaQuery().eq(InfoDO::getId, id));
    }

    @Transactional(rollbackFor = Exception.class)
    public void copy(InfoDTO param) {
        InfoDO byId = this.getById(param.getId());
        if (byId == null) {
            throw new FrameworkException(InfoExceptionInfo.INFO_NOT_FOUND);
        }
        String simpleUuid = IdUtil.simpleUUID();
        byId.setId(simpleUuid);
        byId.setName(byId.getName() + "副本");
        byId.setShared(false);
        byId.setShareUrl(null);
        byId.setSharePassword(null);

        HashMap<String, String> componentsMap = new HashMap<>(4);
        List<ComponentDO> componentDOList = componentService.getListByScreenId(param.getId());
        if (!componentDOList.isEmpty()) {
            componentDOList.forEach(e -> {
                String componentId = IdUtil.simpleUUID();
                componentsMap.put(e.getId(), componentId);
                e.setId(componentId);
                e.setScreenId(simpleUuid);
            });
            componentService.saveBatch(componentDOList);

            String pages = byId.getPages();
            if (Tools.Str.isNotBlank(pages)) {
                List<EditorPage> list = JSONUtil.toList(pages, EditorPage.class);
                list.forEach(e -> e.setChildren(e.getChildren().stream().map(componentsMap::get).collect(Collectors.toList())));
                byId.setPages(Tools.Json.toJsonStr(list));
            }
        }
        this.baseMapper.insert(byId);

        List<FilterDO> filterDOList = filterService.getListByScreenId(param.getId());
        if (!filterDOList.isEmpty()) {
            filterDOList.forEach(e -> {
                e.setScreenId(simpleUuid);
                e.setId(IdUtil.simpleUUID());
            });
            filterService.saveBatch(filterDOList);
        }
    }

    public InfoDO supply(InfoDTO param) {
        InfoDO info = new InfoDO();
        info.setId(param.getId());
        info.setScreenId(param.getScreenId());
        info.setName(param.getName());
        info.setGroupId(param.getGroupId());
        info.setTemplateId(param.getTemplateId());
        info.setShareUrl(param.getShareUrl());
        info.setSharePassword(param.getSharePassword());

        info.setShareTime(param.getShareTime());
        info.setShared(param.getShared());
        info.setWidth(param.getWidth());
        info.setHeight(param.getHeight());
        info.setBgimage(param.getBgimage());
        info.setBgcolor(param.getBgcolor());
        info.setGrid(param.getGrid());
        info.setScreenshot(ObjectUtil.defaultIfBlank(param.getScreenshot(), FileConstant.LOGO_SOURCE));
        info.setUseWatermark(param.getUseWatermark());
        info.setThumbnail(param.getThumbnail());
        info.setZoomMode(param.getZoomMode());
        info.setDefaultPage(param.getDefaultPage());

        info.setDialogs(Tools.Json.toJsonStr(param.getDialogs()));
        info.setEvents(Tools.Json.toJsonStr(param.getEvents()));
        info.setHost(Tools.Json.toJsonStr(param.getHost()));
        info.setIframe(Tools.Json.toJsonStr(param.getIframe()));
        info.setPages(Tools.Json.toJsonStr(param.getPages()));
        info.setStyleFilterParams(Tools.Json.toJsonStr(param.getStyleFilterParams()));
        info.setVariables(Tools.Json.toJsonStr(param.getVariables()));
        return info;
    }

    @Transactional(rollbackFor = Exception.class)
    public InfoDO create(InfoDTO param) {

        // 获取模板基本信息
        TemplateVO template = templateService.getInfo(param.getTemplateId());

        InfoDO infoDO = JSONUtil.toBean(template.getConfig(), InfoDO.class);
        infoDO.setId(Tools.Id.uuid());
        infoDO.setGroupId(param.getGroupId());
        infoDO.setName(param.getName());
        infoDO.setShareUrl(this.getShareUrl(infoDO.getId()));
        infoDO.setPages(this.convertPage(infoDO.getPages(), infoDO.getId()));


        this.save(infoDO);

        List<FilterDO> filterDOList = filterService.getListByTemplateId(param.getTemplateId());
        if (!filterDOList.isEmpty()) {
            filterDOList.forEach(e -> {
                e.setTemplateId(null);
                e.setScreenId(infoDO.getId());
                e.setId(IdUtil.simpleUUID());
                e.setFilterType(FilterTypeEnum.COMMON.name());
            });
            filterService.saveBatch(filterDOList);
        }
        return infoDO;
    }

    private String convertPage(String pagesJsonStr, String infoId) {
        List<EditorPage> list = JSONUtil.toList(pagesJsonStr, EditorPage.class);
        if (Tools.Coll.isNotBlank(list)) {
            list.forEach(e -> {
                // 取出屏幕上所有的子元素
                List<TemplateComponentDO> templateComponents = templateComponentService.getComponent(e.getChildren());
                // 子元素替换ID和插入新的组件
                List<ComponentDO> componentList = Tools.Coll.convertList(templateComponents, component -> this.convertNewComponent(component, infoId));
                e.setChildren(Tools.Coll.convertList(componentList, ComponentDO::getId));
                componentService.saveBatch(componentList);
            });
        }
        return Tools.Json.toJsonStr(list);
    }

    private ComponentDO convertNewComponent(TemplateComponentDO component, String screenId) {
        ComponentDO entity = BeanUtil.copyProperties(component, ComponentDO.class);
        entity.setScreenId(screenId);
        entity.setId(Tools.Str.removePrefix(component.getName(), "V") + "_" + RandomUtil.randomString(9));
        // 必须先覆盖修改子组件的ID
        entity.setChildren(JSONUtil.toJsonStr(convertComponentChild(entity.getChildren(), entity.getId(), screenId)));
        return entity;
    }

    /**
     * 重置组件的子组件中的各类ID
     *
     * @return 转为Json的新数据
     */
    private List<Object> convertComponentChild(String childJsonStr, String parentId, String screenId) {
        if(!JSONUtil.isTypeJSON(childJsonStr)){
            return new ArrayList<>();
        }
        List<Object> list = Util.Json.toBean(childJsonStr);

        if (Tools.Coll.isBlank(list)) {
            return new ArrayList<>();
        }
        for (Object entity : list) {
            JSONObject json = (JSONObject) entity;
            json.set("parentId", parentId);
            json.set("screenId", screenId);
            json.set("children", convertComponentChild(JSONUtil.toJsonStr(json.get("children")), (String) json.get("id"), screenId));
        }
        return list;
    }


    public void share(InfoDTO param) {
        InfoDO byId = this.getById(param.getId());
        if (byId == null) {
            throw new FrameworkException(InfoExceptionInfo.INFO_NOT_FOUND);
        }
        byId.setShared(param.getShared());
        byId.setShareUrl(param.getShareUrl());
        byId.setSharePassword(param.getSharePassword());
        this.baseMapper.updateById(byId);
    }

    public InfoVO getInfoByTemplateId(String id) {
        TemplateVO template = templateService.getInfo(id);
        InfoDO infoDO = JSONUtil.toBean(template.getConfig(), InfoDO.class);
        infoDO.setId(id);
        return this.convertVO(infoDO);
    }
}
